﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ZeroBugBounce.Missive.Locks;

namespace ZeroBugBounce.Missive
{
	public abstract class TimeoutHandlerBase<T> : Handler<T>
	{
		protected internal TimeSpan Timeout;
		EventHandler<TimeoutEventArgs<T>> handlerTimeout;
		readonly ILock handlerTimeoutLock = new NoSpinHybridLock();

		public event EventHandler<TimeoutEventArgs<T>> HandlerTimeout
		{
			add
			{
				handlerTimeoutLock.Enter();
				if (handlerTimeout == null && value != null)
				{
					handlerTimeout = value;
				}
				else if (value != null)
				{
					handlerTimeout = (EventHandler<TimeoutEventArgs<T>>)Delegate.Combine(handlerTimeout, value);
				}
				handlerTimeoutLock.Leave();
			}
			remove
			{
				handlerTimeoutLock.Enter();
				if (handlerTimeout == null)
				{
					return;
				}
				else
				{
					handlerTimeout = (EventHandler<TimeoutEventArgs<T>>)Delegate.Remove(handlerTimeout, value);
				}
				handlerTimeoutLock.Leave();
			}
		}

		public TimeoutHandlerBase(TimeSpan timeout)
		{
			this.Timeout = timeout;
		}

		protected void RaiseTimeoutError(T message)
		{
			EventHandler<TimeoutEventArgs<T>> handlerTimeoutHandlers =
				Interlocked.CompareExchange<EventHandler<TimeoutEventArgs<T>>>(ref handlerTimeout, null, null);

			if (handlerTimeoutHandlers != null)
			{
				handlerTimeoutHandlers(this, new TimeoutEventArgs<T>(message));
			}
		}

		protected internal abstract void HandleWithTimeout(HandlerContext<T> context);
	}

	public class TimeoutEventArgs<T> : EventArgs
	{
		public TimeoutEventArgs(T message)
		{
			Message = message;
		}

		public T Message { get; internal set; }
	}

	public abstract class TimeoutHandler<T> : TimeoutHandlerBase<T>
	{
		public TimeoutHandler(TimeSpan timeout)
			: base(timeout)
		{

		}

		public override void Handle(HandlerContext<T> context)
		{
			var timeoutTask = new Task(() =>
			{
				HandleWithTimeout(context);
			}, TaskCreationOptions.LongRunning);

			timeoutTask.Start();
			if (!timeoutTask.Wait((int)Timeout.TotalMilliseconds))
			{
				RaiseTimeoutError(context.Message);
			}
		}
	}

	public class LambdaTimeoutHandler<T> : TimeoutHandler<T>
	{
		Action<T> handlerAction;
		public LambdaTimeoutHandler(Topic topic, DispatchMode dispatchMode, TimeSpan timeout, 
			Action<T> action) : base(timeout)
		{
			Topic = topic;
			handlerAction = action;
			DispatchMode = dispatchMode;
		}

		public LambdaTimeoutHandler(Topic topic, DispatchMode dispatchMode, TimeSpan timeout, 
			Action<T> action, EventHandler<TimeoutEventArgs<T>> timeoutAction) : base(timeout)
		{
			Topic = topic;
			handlerAction = action;
			DispatchMode = dispatchMode;
			HandlerTimeout += timeoutAction;
		}

		protected internal override void HandleWithTimeout(HandlerContext<T> context)
		{
			handlerAction(context.Message);
		}
	}
}
